#include "VirtualMachine.h"
#include "Stack.h"
#include "neiderra/utils/Logger.h"
#include "neiderra/utils/CommonFunctions.h"
#include "neiderra/core/io/IOStream.h"
#include "neiderra/core/iException.h"

using namespace neiderra::vm;
using namespace neiderra::core;
using namespace neiderra::utils;

#define log(s) core->getLogger()->log(s)
#define trace(s) core->getLogger()->traceBlock(s)
#define leave core->getLogger()->leaveBlock()

VirtualMachine::VirtualMachine(Core* core, unsigned heapSize, unsigned stackSize, unsigned retStackSize)
	: Base(core)
{
	_stack = new Stack(stackSize);
	_regstack = new Stack(stackSize);
	_flagstack = new Stack(stackSize);
	_heap = new nByte[heapSize];
	_retstack = new Stack(retStackSize);
	ptr_act = 0;
	//unsigned a = ptr_act;
//	_log = new Logger(Core::Instance);
	r = new int[VM_REGISTERS_NUM];
	flags = new bool[8];
	resetFlags();
	_stop = false;

	//iOutputStream* _out = Core::Instance->createOutputStream();
	//_out->open("logs/vm.log");
	//_log->setOutput(_out);
	_pc=0;
}

VirtualMachine::~VirtualMachine()
{
	delete _stack;
	delete _flagstack;
	delete _regstack;
	delete _retstack;
	delete[] _heap;
	delete[] r;
	delete[] flags;
}

void VirtualMachine::resetFlags()
{
	for(int i=0;i<8;i++) flags[i]=0;
}

void VirtualMachine::setProgram(nByte* bytecode, unsigned programSize)
{
	_bytecode = bytecode;
	_programSize = programSize;
}

int VirtualMachine::run()
{
	trace("VirtualMachine::run");
	
	if(!_bytecode)
	{
		leave;
		return 2;
	}

	try{
		if(_bytecode[0] == 0)
			_pc = 5;
		
		while(_pc < _programSize-1 && !_stop)
			execute();
	}
	catch(Stack::overflow e)
	{
		ex_fatal("stack overflow");
	}
	catch(Stack::underflow e)
	{
		ex_fatal("stack underflow");
	}

	leave;
	return 1;
}

int VirtualMachine::getInt(nByte* buf)
{
    nByte b[4];
	b[0] = *(buf+0);
	b[1] = *(buf+1);
	b[2] = *(buf+2);
	b[3] = *(buf+3);
	return *(reinterpret_cast<int*>(b));
}

VirtualMachine::Command VirtualMachine::readCommand()
{
	unsigned char x = (int)_bytecode[_pc];
	if(x > CommandNum) 
		throw exBadOperation();
	return (Command) x;
}

void VirtualMachine::readBytes(nByte* bytes, unsigned size)
{
	for(int i=0;i<size;i++)
		bytes[i]=_bytecode[_pc+i];
}

void VirtualMachine::pushRegisters()
{
	for(int i=0;i<VM_REGISTERS_NUM;i++)
		_regstack->push(r[i]);
}

void VirtualMachine::popRegisters()
{
	for(int i=VM_REGISTERS_NUM-1;i>=0;i--)
		r[i]=_regstack->popi();
}

void VirtualMachine::pushFlags()
{
	for(int i=0;i<8;i++)
		_flagstack->push(flags[i]);
}

void VirtualMachine::popFlags()
{
	for(int i=7;i>=0;i--)
		flags[i]=_flagstack->popi();
}

void VirtualMachine::pushState()
{
}

void VirtualMachine::popState()
{
}

float VirtualMachine::getFloat(nByte*)
{
	return 0;
}

int VirtualMachine::execute()
{
	try{

// 		logState();
		Command cmd = readCommand();

		if(cmd == VirtualMachine::cmdPUSH)
		{
			log("push");
			unsigned addr = getInt(&_bytecode[_pc+1]);
//			nByte addr = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+5];
			
			if(size==sizeof(int))
			{
				int val = _stack->geti(ptr_act + addr);
				_stack->push(val);
			}
			_pc+=6;
		}
		else if(cmd == VirtualMachine::cmdPUSHR)
		{
			log("pushr");
			nByte size = _bytecode[_pc+1];
			nByte regNum = _bytecode[_pc+2];
            _stack->push(r[regNum]);
			_pc+=3;
		}
		else if(cmd == VirtualMachine::cmdPOPR)
		{
			log("popr");
			nByte regNum = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+2];
			if(size = 4){
				r[regNum] = _stack->popi();
			}
			_pc+=3;
		}
		else if(cmd == VirtualMachine::cmdPOP)
		{
			log("pop");
			unsigned addr = getInt(&_bytecode[_pc+1]);
//			nByte addr = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+5];
			
			if(size==sizeof(int))
			{
				// небольшой хак. если будет вот так:
				int val = _stack->popi();
				// то, мы получим переполнение стека, т.к. 
				_stack->set(ptr_act+addr, val);
				_pc+=6;
			}
		}
		else if(cmd == VirtualMachine::cmdTOPR)
		{
			log("topr");
			nByte regNum = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+2];
			if(size = 4){
				r[regNum] = _stack->topi();
			}
			_pc+=3;
		}
		else if(cmd == VirtualMachine::cmdTOP)
		{
			log("top");
			unsigned addr = getInt(&_bytecode[_pc+1]);
//			nByte addr = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+5];
			
			if(size==sizeof(int))
			{
				// небольшой хак. если будет вот так:
				int val = _stack->topi();
				// то, мы получим переполнение стека, т.к. 
				_stack->set(ptr_act+addr, val);
				_pc+=6;
			}
		}
		else if(cmd == VirtualMachine::cmdLD)
		{
			log("ld");
			nByte reg = _bytecode[_pc+1];
			nByte size = _bytecode[_pc+6];
			int val = getInt(&_bytecode[_pc+2]);
			r[reg]=val;
			_pc+=7;
		}
		else if(cmd == VirtualMachine::cmdMOV)
		{
			log("mov");
			nByte addr1 = _bytecode[_pc+1];
			nByte addr2 = _bytecode[_pc+2];
			int val = r[addr2];
			r[addr1] = val;
			_pc+=3;
		}
		else if(cmd == VirtualMachine::cmdDATA)
		{
			log("data");
			nByte bytes_num = _bytecode[_pc+1];
			nByte* buf = new nByte[bytes_num];
			_stack->push(bytes_num, buf);
			delete[] buf;
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdNOP)
		{
			log("nop");
			_pc++;
		}
		else if(cmd == VirtualMachine::cmdADD)
		{
			log("add");
			nByte size = _bytecode[_pc+1];
			if(size == sizeof(int))
			{
				int a = _stack->popi();
				int b = _stack->popi();
				_stack->push(a+b);
			}
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdMUL)
		{
			log("mul");
			nByte size = _bytecode[_pc+1];
			if(size == sizeof(int))
			{
				int a = _stack->popi();
				int b = _stack->popi();
				_stack->push(a*b);
			}
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdDIV)
		{
			log("div");
			nByte size = _bytecode[_pc+1];
			if(size == sizeof(int))
			{
				int a = _stack->popi();
				int b = _stack->popi();
				if(b != 0)
					_stack->push(a/b);
				else 
					ex_fatal("divide by zero");
			}
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdSUB)
		{
			log("sub");
			nByte size = _bytecode[_pc+1];
			if(size == sizeof(int))
			{
				int a = _stack->popi();
				int b = _stack->popi();
				_stack->push(a-b);
			}
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdBRNEQ)
		{
			log("brneq");
			unsigned addr = (unsigned) getInt(&_bytecode[_pc+1]);
			if(flags[flagZero] == false)
				_pc = addr;
			else _pc+=5;
		}
		else if(cmd == VirtualMachine::cmdBREQ)
		{
			log("breq");
			unsigned addr = (unsigned) getInt(&_bytecode[_pc+1]);
			if(flags[flagZero] == true)
				_pc = addr;
			else _pc+=5;
		}
		else if(cmd == VirtualMachine::cmdGOTO)
		{
			log("goto");
			unsigned addr = (unsigned) getInt(&_bytecode[_pc+1]);
			_pc = addr;
		}
		else if(cmd == VirtualMachine::cmdORG)
		{
			// not implemented
			log("org");
			unsigned addr = (unsigned) getInt(&_bytecode[_pc+1]);
			
		}
		else if(cmd == VirtualMachine::cmdCP)
		{
			log("cp");
			nByte r1 = _bytecode[_pc+1];
			nByte r2 = _bytecode[_pc+2];
			flags[flagZero]=(r[r1]==r[r2])?1:0;
			_pc+=3;
		}
		else if(cmd == VirtualMachine::cmdCALL)
		{
			log("call");
			/*
				I. Create the procedure activation:
					1. save registers;
					2. save return address;
					3. save activation pointer;
					4. save flags;
				II. Call procedure;
			*/
			
            // save registers
            pushRegisters();

			// save return address
			_retstack->push(int(_pc+5));
			
			// save & update activation pointer
			_retstack->push((int)ptr_act);	// !
			
			ptr_act = _stack->ptrTop();
			
			// save flags (after registers!)
			pushFlags();

			// call proc
            unsigned addr = (unsigned) getInt(&_bytecode[_pc+1]);
			_pc = addr;
		}
		else if(cmd == VirtualMachine::cmdRET)
		{
			log("ret");
			/*
				I. Close current activation:
					1. get parent activation pointer;
					2. get return address; 
					3. pop flags;
					4. pop registers;
				II. Continue working.
			*/
	
			// get activation pointer
			try{
				ptr_act = _retstack->popi();
			}
			catch(Stack::underflow fl)
			{
				_stop = true;
				return 0;
			}
			
			// get return addres
			_pc = _retstack->popi();
		
			// pop flags (before registers!)
			popFlags();
			// pop registers
			popRegisters();
			
		}
		else if(cmd == VirtualMachine::cmdDEC)
		{
			log("dec");
			nByte reg = _bytecode[_pc+1];
			r[reg]--;
			_pc+=2;
		}
		else if(cmd == VirtualMachine::cmdST)
		{
			log("st");
			unsigned offset = getInt(&_bytecode[_pc+1]);
			nByte reg_num = _bytecode[_pc+5];
			nByte size = _bytecode[_pc+6];
			unsigned st_value = r[reg_num];
			//_stack->set(ptr_act+offset, size, reinterpret_cast<nByte*>(&st_value));
			_stack->set(ptr_act+offset, st_value);
			_pc+=7;
		}
		else if(cmd == VirtualMachine::cmdLOG)
		{
			log("log");
			unsigned offset = getInt(&_bytecode[_pc+1]);
			nByte size = _bytecode[_pc+5];
			if(size == sizeof(int)){
				int val = _stack->geti(ptr_act+offset);
//				core->getCoreLogger()->log(val);
				log(val);
			}
			_pc+=6;
		}
	}
	catch(exBadOperation e)
	{
		core->getLogger()->fatal("Virtual Machine: Your program has performed an impossible operation and will be closed");
		throw;
	}
	return 1;	
}

void VirtualMachine::logState()
{
	string state = "stack top: ";
	state += intToStr(_stack->ptrTop());
	log(state);
}

